﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace OxyPlot.CatExtension.Skia
{
    internal sealed class SimdDecimatorF
    {
        public const int MIN_DECIMATE_COUNT = 512;
        public const int MIN_DECIMATE_PARALLEL_COUNT = 1024 * 8;

        private static readonly int ProcessorCount = Environment.ProcessorCount;
        private readonly List<float>[] _buffersX;
        private readonly List<float>[] _buffersY;

        private float offset_x;
        private float scale_x;

        private float offset_y;
        private float scale_y;

        public SimdDecimatorF()
        {            
            _buffersX = new List<float>[ProcessorCount];
            _buffersY = new List<float>[ProcessorCount];
            for (int i = 0; i < ProcessorCount; i++)
            {
                _buffersX[i] = [];
                _buffersY[i] = [];
            }
        }

        public void Decimate(List<float> pointsX, List<float> pointsY, int start, int end, float offsetX, float scaleX, float offsetY, float scaleY, List<float> outputX, List<float> outputY)
        {
            if (pointsX == null || pointsX.Count == 0)
            {
                return;
            }

            offset_x = offsetX;
            scale_x = scaleX;
            offset_y = offsetY;
            scale_y = scaleY;

            var count = end - start + 1;

            if (count <= MIN_DECIMATE_COUNT)
            {
                for (int i = start; i <= end; i++)
                {
                    outputX.Add(pointsX[i]);
                    outputY.Add(pointsY[i]);                    
                }
                SIMD.Transform(CollectionsMarshal.AsSpan(outputX), scale_x, -scaleX * offsetX);
                SIMD.Transform(CollectionsMarshal.AsSpan(outputY), scale_y, -scaleY * offsetY);
            }
            else if (count <= MIN_DECIMATE_PARALLEL_COUNT)
            {
                DecimateCore(pointsX, pointsY, start, end, outputX, outputY);
            }
            else
            {
                DecimateParallel(pointsX, pointsY, start, end, outputX, outputY);
            }

        }

        private void DecimateCore(List<float> pointsX, List<float> pointsY, int start, int end, List<float> outputX, List<float> outputY)
        {            
            var xSpan = CollectionsMarshal.AsSpan(pointsX).Slice(start, end - start + 1);
            var ySpan = CollectionsMarshal.AsSpan(pointsY).Slice(start, end - start + 1);

            start = 0;
            int currentX = (int)TransformX(xSpan[0]);

            var minY = ySpan[start];
            var maxY = minY;

            for (int i = 1; i < xSpan.Length; i++)
            {
                var x = TransformX(xSpan[i]);
                if (currentX != (int)x)
                {
                    var startY = ySpan[start];
                    if (minY != startY)
                    {
                        outputX.Add(currentX);
                        outputY.Add(TransformY(minY));
                        outputX.Add(currentX);
                        outputY.Add(TransformY(startY));

                        if (maxY != startY)
                        {
                            outputX.Add(currentX);
                            outputY.Add(TransformY(maxY));                            
                        }
                    }
                    else
                    {
                        outputX.Add(currentX);
                        outputY.Add(TransformY(minY));                        
                        if (maxY != minY)
                        {
                            outputX.Add(currentX);
                            outputY.Add(TransformY(maxY));                            
                        }
                    }

                    currentX = (int)x;
                    start = i;
                    minY = ySpan[start];
                    maxY = minY;
                }
                else
                {
                    minY = Math.Min(minY, ySpan[i]);
                    maxY = Math.Max(maxY, ySpan[i]);
                }
            }

            outputX.Add(TransformX(xSpan[^1]));
            outputY.Add(TransformY(ySpan[^1]));            
        }

        private void DecimateParallel(List<float> pointsX, List<float> pointsY, int start, int end, List<float> outputX, List<float> outputY)
        {
            var len = (end - start + 1) / ProcessorCount;
            var lastLen = (end - start + 1) % ProcessorCount + len;
            Parallel.For(0, ProcessorCount, i =>
            {
                var bufferX = _buffersX[i];
                var bufferY = _buffersY[i];

                Span<float> xSpan;
                Span<float> ySpan;

                if (i == ProcessorCount - 1)
                {
                    xSpan = CollectionsMarshal.AsSpan(pointsX).Slice(start + i * len, lastLen);
                    ySpan = CollectionsMarshal.AsSpan(pointsY).Slice(start + i * len, lastLen);
                }
                else
                {
                    xSpan = CollectionsMarshal.AsSpan(pointsX).Slice(start + i * len, len);
                    ySpan = CollectionsMarshal.AsSpan(pointsY).Slice(start + i * len, len);
                }

                int currentX = (int)TransformX(xSpan[0]);

                int startIndex = 0;
                var minY = ySpan[startIndex];
                var maxY = minY;

                for (int j = 1; j < xSpan.Length; j++)
                {
                    var x = TransformX(xSpan[j]);
                    if (currentX != (int)x)
                    {
                        var startY = ySpan[startIndex];
                        if (minY != startY)
                        {
                            bufferX.Add(currentX);
                            bufferY.Add(TransformY(minY));
                            bufferX.Add(currentX);
                            bufferY.Add(TransformY(startY));                            
                            if (maxY != startY)
                            {
                                bufferX.Add(currentX);
                                bufferY.Add(TransformY(maxY));                                
                            }
                        }
                        else
                        {
                            bufferX.Add(currentX);
                            bufferY.Add(TransformY(minY));
                            if (maxY != minY)
                            {
                                bufferX.Add(currentX);
                                bufferY.Add(TransformY(maxY));
                            }
                        }

                        currentX = (int)x;
                        startIndex = j;
                        minY = ySpan[startIndex];
                        maxY = minY;
                    }
                    else
                    {
                        minY = Math.Min(minY, ySpan[j]);
                        maxY = Math.Max(maxY, ySpan[j]);
                    }
                }

                bufferX.Add(TransformX(xSpan[^1]));
                bufferY.Add(TransformY(ySpan[^1]));                
            });

            for (int i = 0; i < _buffersX.Length; i++)
            {
                outputX.AddRange(_buffersX[i]);
                _buffersX[i].Clear();

                outputY.AddRange(_buffersY[i]);
                _buffersY[i].Clear();
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private float TransformX(float x)
        {
            return (x - offset_x) * scale_x;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private float TransformY(float y)
        {
            return (y - offset_y) * scale_y;
        }
    }
}
